package com.ycb.concurrency.limitation.rate;


import lombok.extern.slf4j.Slf4j;

/**
 * 滑动时间窗口限流
 */
@Slf4j
public class SlidingTimeRateLimiter implements RateLimiter {

    // 滑动窗口总分块个数
    private int initialCapacity = 60;
    private int[] slidingWindow;
    // 存储当前窗口请求量游标
    private int currentCur = 0;
    // 滑动窗口分块边界
    private long left = 0, right = 0;
    // 滑动窗口分块大小
    private int minWindowSize = 10;
    // 当前累计请求量
    private int requestCount = 0;
    // 窗口请求量限制
    private int maxCount = 10;

    /**
     * 滑动时间窗口限流
     * @param initialCapacity   初始化容量
     * @param minWindowSize     最小窗口数
     * @param maxCount          最大请求数
     */
    public SlidingTimeRateLimiter(int initialCapacity, int minWindowSize, int maxCount) {
        this.initialCapacity = initialCapacity;
        this.slidingWindow = new int[initialCapacity];
        this.minWindowSize = minWindowSize;
        this.maxCount = maxCount;
    }


    private void refreshWindow(long now) {
        if (right == 0) {
            right = now;
        } else {
            do {
                currentCur = (currentCur + 1) % initialCapacity;
                requestCount -= slidingWindow[currentCur];
                slidingWindow[currentCur] = 0;
                right += minWindowSize;
                log.warn("time window reload timestamp:" + now + " left:" + left + " right:" + right);
            } while (now > right);
        }
        left = right - minWindowSize;
    }

    /*
    now ∈ (left, right] 判断请求量是否达到上限，否则该窗口请求加一
    now > right 移动到后面的窗口，并清空最初窗口的累计值
    */
    @Override
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        if (now > right) {
            refreshWindow(now);
        }
        if (requestCount < maxCount) {
            slidingWindow[currentCur]++;
            requestCount++;
            log.info("try acquire success! current requestCount:" + requestCount + " current:" + now + " left:" + left + " right:" + right);
            return true;
        } else {
            log.error("try acquire fail. current:" + now + " left:" + left + " right:" + right);
            return false;
        }
    }
}

